home *** CD-ROM | disk | FTP | other *** search
/ Aminet 45 / Aminet 45 (2001)(GTI - Schatztruhe)[!][Oct 2001].iso / Aminet / game / role / ldmud-3.2-bin.lha / mud / doc / LPC / closures-example < prev    next >
Text File  |  2001-04-06  |  8KB  |  203 lines

  1. CONCEPT
  2.         closures example
  3.  
  4. DESCRIPTION
  5.         This document contains small examples of the usage of
  6.         (lambda-)closures. For technical details see the closures(LPC)
  7.         doc. For hints when to use which type of closure, see the end
  8.         of this doc.
  9.  
  10.  
  11.         Many Muds use 'details' to add more flavour. 'Details' are
  12.         items which can be looked at, but are not implemented as own
  13.         objects, but instead simulated by the environment.
  14.         Lets assume that the function
  15.  
  16.           AddDetail(string keyword, string|closure desc)
  17.  
  18.         adds the detail 'keyword' to the room, which, when look at,
  19.         returns the string 'desc' resp. the result of the execution of
  20.         closure 'desc' as the detail description to the player.
  21.  
  22.         Now imagine that one wants to equip a room with magic runes,
  23.         which read as 'Hello <playername>!\n" when looked at.
  24.         Obviously
  25.  
  26.           AddDetail("runes", sprintf( "Hello %s!\n"
  27.                                     , this_player()->QueryName()));
  28.  
  29.         is not sufficient, as the 'this_player()' is executed to early
  30.         and just once: for the player loading the room.
  31.  
  32.         The solution is to use closures. First, the solution using
  33.         lfun-closures:
  34.  
  35.           private string _detail_runes () {
  36.             return sprintf("Hello %s!\n", this_player()->QueryName());
  37.           }
  38.             ...
  39.           AddDetail("runes", #'_detail_runes);
  40.  
  41.         or with an inline closure:
  42.  
  43.           AddDetail("runes"
  44.                    , (: sprintf("Hello %s!\n", this_player()->QueryName()) :)
  45.                    );
  46.  
  47.  
  48.         Simple? Here is the same code, this time as lambda-closure:
  49.  
  50.           AddDetail( "runes"
  51.                    , lambda(0
  52.                      , ({#'sprintf, "Hello %s!\n"
  53.                                   , ({#'call_other, ({#'this_player})
  54.                                                   , "QueryName" })
  55.                        })
  56.                    ));
  57.  
  58.         Why the extra ({ }) around '#'this_player'? #'this_player
  59.         alone is just a symbol, symbolizing the efun this_player(),
  60.         but call_other() needs an object as first argument. Therefore,
  61.         the #'this_player has to be interpreted as function to
  62.         evaluate, which is enforced by enclosing it in ({ }). The same
  63.         reason also dictates the enclosing of the whole #'call_other
  64.         expression into ({ }).
  65.         Note also the missing #'return: it is not needed. The result
  66.         of a lambda-closure is the last value computed.
  67.  
  68.         
  69.         Another example: Task is to reduce the HP of every living in a
  70.         room by 10, unless the result would be negative.
  71.         Selecting all livings in a room is simply
  72.  
  73.           filter_array(all_inventory(room), #'living)
  74.  
  75.         The tricky part is to reduce the HP. Again, first the
  76.         lfun-closure solution:
  77.  
  78.           private _reduce_hp (object liv) {
  79.             int hp;
  80.             hp = liv->QueryHP();
  81.             if (hp > 10)
  82.               liv->SetHP(hp-10);
  83.           }
  84.             ...
  85.  
  86.           map_array( filter_array(all_inventory(room), #'living)
  87.                    , #'_reduce_hp)
  88.  
  89.         or as an inline closure:
  90.  
  91.           map_array( filter_array(all_inventory(room), #'living)
  92.                    , (: int hp;
  93.                         hp = liv->QueryHP();
  94.                         if (hp > 10)
  95.                           liv->SetHP(hp - 10);
  96.                       :) );
  97.  
  98.         Both filter_array() and map_array() pass the actual array item
  99.         being filtered/mapped as first argument to the closure.
  100.  
  101.         Now, the lambda-closure solution:
  102.  
  103.           map_array( filter_array(all_inventory(room), #'living)
  104.           , lambda( ({ 'liv })
  105.             , ({'#, , ({#'=, 'hp, ({#'call_other, 'liv, "QueryHP" }) })
  106.                     , ({#'?, ({#'>, 'hp, 10 })
  107.                            , ({#'call_other, 'liv, "SetHP"
  108.                                            , ({#'-, 'hp, 10 })
  109.                              })
  110.                       })
  111.               })
  112.             ) // of lambda()
  113.           );
  114.         
  115.         It is worthy to point out how local variables like 'hp' are
  116.         declared in a lambda-closure: not at all. They are just used
  117.         by writing their symbol 'hp . Same applies to the closures
  118.         parameter 'liv .
  119.         The lambda-closure solution is not recommended for three
  120.         reasons: it is complicated, does not use the powers of
  121.         lambda(), and the lambda() is recompiled every time this
  122.         statement is executed!
  123.  
  124.         
  125.         So far, lambda-closures seem to be just complicated, and in
  126.         fact: they are. Their powers lie elsewhere.
  127.  
  128.         Imagine a computation, like for skill resolution, which
  129.         involves two object properties multiplied with factors and
  130.         then added.
  131.         The straightforward solution would be a function like:
  132.  
  133.           int Compute (object obj, string stat1, int factor1
  134.                                  , string stat2, int factor2)
  135.           {
  136.             return   call_other(obj, "Query"+stat1) * factor1
  137.                    + call_other(obj, "Query"+stat2) * factor2;
  138.           }
  139.  
  140.         Each call to Compute() involves several operations (computing
  141.         the function names and resolving the call_other()s) which in
  142.         fact need to be done just once. Using lambda-closures, one can
  143.         construct and compile a piece of code which behaves like a
  144.         Compute() tailored for a specific stat/factor combination:
  145.  
  146.           closure ConstructCompute (object obj, string stat1, int factor1
  147.                                               , string stat2, int factor2)
  148.           {
  149.             mixed code;
  150.  
  151.             // Construct the first multiplication.
  152.             // The symbol_function() creates a symbol for the
  153.             // lfun 'Query<stat1>', speeding up later calls.
  154.             // Note again the extra ({ }) around the created symbol.
  155.  
  156.             code = ({#'*, ({ symbol_function("Query"+stat1, obj) })
  157.                         , factor1 });
  158.  
  159.             // Construct the second multiplication, and the addition
  160.             // of both terms.
  161.  
  162.             code = ({#'+, code
  163.                         , ({#'*, ({ symbol_function("Query"+stat2, obj) })
  164.                                , factor2 })
  165.                    });
  166.  
  167.             // Compile the code and return the generated closure.
  168.             return lambda(0, code);
  169.           }
  170.  
  171.         Once the closure is compiled,
  172.  
  173.           str_dex_fun = ConstructCompute(obj, "Str", 10, "Dex", 90);
  174.  
  175.         it can be used with a simple 'funcall(str_dex_fun)'.
  176.  
  177.  
  178. DESCRIPTION -- When to use which closure?
  179.         First, a closure is only then useful if it needn't to live any
  180.         longer than the object defining it. Reason: when the defining
  181.         object gets destructed, the closure will vanish, too.
  182.         
  183.         Efun-, lfun- and inline closures should be used where useful, as they
  184.         mostly do the job and are easy to read. The disadvantage of lfun- and
  185.         inline closures is that they make a replace_program() impossible
  186.         - but since such objects tend to not being replaceable at all, this is
  187.         no real loss.
  188.  
  189.         Lambda closures are needed if the actions of the closure are
  190.         heavily depending on some data available only at runtime, like
  191.         the actual inventory of a certain player.
  192.         If you use lfun-closures and find yourself shoving around
  193.         runtime data in arguments or (gasp!) global variables, it is
  194.         time to think about using a lambda-closure, compiling the
  195.         value hard into it.
  196.         The disadvantages of lambda closures are clear: they are damn
  197.         hard to read, and each lambda() statement requires extra time to
  198.         compile the closure.
  199.  
  200.  
  201. SEE ALSO
  202.         closures(LPC), closure_guide(LPC), closures-abstract(LPC)
  203.